A common approach to determine the cost of products is the should cost method. It consists in estimating what a product should cost based on materials, labor, overhead, and profit margin. Although this strategy is very accurate, it has the drawback of being tedious and it requires expert knowledge of industrial technologies and processes. To get a quick estimation, it is possible to build a statistical model to predict the price of products given their characteristics. With such a model, it would no longer be necessary to be an expert or to wait several days to assess the impact of a design modification, a change in supplier or a change in production site. Before builing a model, it is important to explore the data which is the aim of this case study. This study was commissioned by a cosmetics company that wants to estimate the price of Screw Caps of shampoo bottles.

Let’s first load the database study it’s structure and load the différent packages.

#Loading the different packages for this study 
library(dplyr)
le package ‘dplyr’ a été compilé avec la version R 3.4.2
Attachement du package : ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(readr)
library(ggplot2)
library(FactoMineR)
le package ‘FactoMineR’ a été compilé avec la version R 3.4.2
library(cluster)
library(fpc)
library(factoextra)
Welcome! Related Books: `Practical Guide To Cluster Analysis in R` at https://goo.gl/13EFCZ
library(FactoInvestigate)
le package ‘FactoInvestigate’ a été compilé avec la version R 3.4.2
library(plotly)

Attachement du package : ‘plotly’

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout

QUESTION 1 : Screw Caps Dataset

Now, we load the dataset used for this study:

#Loading the dataset
dataset <- read.table("ScrewCaps.csv", header = TRUE, sep = ",", dec = ".", row.names = 1)
#Printing the dataset
head(dataset)
#Understanding the structure
print(paste0("DB Dimensions: ", dim(dataset)[1]," X " , dim(dataset)[2] ))
[1] "DB Dimensions: 195 X 11"
summary(dataset)
       Supplier      Diameter          weight       nb.of.pieces        Shape     Impermeability
 Supplier A: 31   Min.   :0.4458   Min.   :0.610   Min.   : 2.000   Shape 1:134   Type 1:172    
 Supplier B:150   1st Qu.:0.7785   1st Qu.:1.083   1st Qu.: 3.000   Shape 2: 45   Type 2: 23    
 Supplier C: 14   Median :1.0120   Median :1.400   Median : 4.000   Shape 3:  8                 
                  Mean   :1.2843   Mean   :1.701   Mean   : 4.113   Shape 4:  8                 
                  3rd Qu.:1.2886   3rd Qu.:1.704   3rd Qu.: 5.000                               
                  Max.   :5.3950   Max.   :7.112   Max.   :10.000                               
        Finishing   Mature.Volume    Raw.Material     Price            Length      
 Hot Printing: 62   Min.   :  1000   ABS: 21      Min.   : 6.477   Min.   : 3.369  
 Lacquering  :133   1st Qu.: 15000   PP :148      1st Qu.:11.807   1st Qu.: 6.161  
                    Median : 45000   PS : 26      Median :14.384   Median : 8.086  
                    Mean   : 96930                Mean   :16.444   Mean   :10.247  
                    3rd Qu.:115000                3rd Qu.:18.902   3rd Qu.:10.340  
                    Max.   :800000                Max.   :46.610   Max.   :43.359  

The data ScrewCap.csv contains 195 lots of screw caps described by 11 variables. Diameter, weight, length are the physical characteristics of the cap; nb.of.pieces corresponds to the number of elements of the cap (the picture above corresponds to a cap with 2 pieces: the valve (clapet) is made of a different material); Mature.volume corresponds to the number of caps ordered and bought by the compagny (number in the lot). All the categorical features are Factors. The other features are numerical.

QUESTION 2 : Univariate and bivariate descriptive statistics

Price distribution

d <- density(dataset$Price)
#Plotting the histogram
hist(dataset$Price, breaks=40, probability = TRUE, main = "Price distribution",
     xlab = "Price")
#Plotting the density
lines(d, col = "red")

We have here a bimodal distribution and we can describe it in more details with the quantiles:

p <- plot_ly(type = 'box') %>%  add_boxplot(y = dataset$Price, jitter = 0.3, pointpos = -1.8, boxpoints = 'all',
              marker = list(color = 'rgb(7,40,89)'),
              line = list(color = 'rgb(7,40,89)'),
              name = "All Points") %>%  layout( title = 'Price Boxplot',  yaxis = list(title = 'Price'))
p

Using this plotly boxplot we notice that we have 25% of the prices between 6.477451 and 11.807022. 50% between 6.477451 and 14.384413 and 75% between 6.477451 and 18.902429. The remaning 25‰ are data located in a wide range of prices between 18.902429 and 46.610372

Price dependency on length

Let’s study now the price dependency on lenght.

p <- ggplot(data=dataset, aes(x= Length, y= Price)) + geom_point(size=1) + geom_smooth(method=lm) + ggtitle(" Price versus lenght ")
ggplotly(p)

fit_price_lenght <- lm(Price~Length, data=dataset)
summary(fit_price_lenght)

Call:
lm(formula = Price ~ Length, data = dataset)

Residuals:
    Min      1Q  Median      3Q     Max 
-13.901  -2.854  -0.741   1.931  16.181 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  8.94613    0.50918   17.57   <2e-16 ***
Length       0.73168    0.03953   18.51   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 4.308 on 193 degrees of freedom
Multiple R-squared:  0.6397,    Adjusted R-squared:  0.6378 
F-statistic: 342.6 on 1 and 193 DF,  p-value: < 2.2e-16

We can observe a dependence. 63.9 % of the variability of the price is explained by the lenght.

Price dependency on weight

Now we study the price dependency on weight.

p <- ggplot(data=dataset, aes(x= weight, y= Price)) + geom_point(size=1) + geom_smooth(method=lm) + ggtitle(" Price versus weight ")
ggplotly(p)

fit_price_weight <- lm(Price~weight, data=dataset)
summary(fit_price_weight)

Call:
lm(formula = Price ~ weight, data = dataset)

Residuals:
     Min       1Q   Median       3Q      Max 
-14.7993  -2.6207  -0.6631   2.5396  13.8357 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   8.2275     0.5602   14.69   <2e-16 ***
weight        4.8312     0.2718   17.78   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 4.419 on 193 degrees of freedom
Multiple R-squared:  0.6208,    Adjusted R-squared:  0.6189 
F-statistic:   316 on 1 and 193 DF,  p-value: < 2.2e-16

We can also observe a dependence. 62 % of the variability of the price is explained by the weight.

Price dependency on Impermeability, Shape and Supplier

Now we will discuss the price dependency on some categorical features such as Impermeability, Shape and Supplier.

p <- plot_ly(type = 'box') %>%  add_boxplot(y = dataset$Price , x = dataset$Impermeability, jitter = 0.3, pointpos = -1.8, boxpoints = 'all',
              marker = list(color = 'rgb(7,40,89)'),
              line = list(color = 'rgb(7,40,89)'),
              name = "Price box") %>%  layout( title = 'Price versus Impermeability Boxplot',  yaxis = list(title = 'Price'))
p
Can't display both discrete & non-discrete data on same axisCan't display both discrete & non-discrete data on same axis

fit_price_impermeability <- lm(Price~ Impermeability, data=dataset)
summary(fit_price_impermeability)

Call:
lm(formula = Price ~ Impermeability, data = dataset)

Residuals:
     Min       1Q   Median       3Q      Max 
-16.4106  -3.0187  -0.6286   2.4897  25.0638 

Coefficients:
                     Estimate Std. Error t value Pr(>|t|)    
(Intercept)           14.7236     0.4117   35.77   <2e-16 ***
ImpermeabilityType 2  14.5846     1.1986   12.17   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 5.399 on 193 degrees of freedom
Multiple R-squared:  0.4341,    Adjusted R-squared:  0.4312 
F-statistic:   148 on 1 and 193 DF,  p-value: < 2.2e-16

The boxplot show us that each impermeability type gather a wide range of prices :

However, we notice a dependence. 43 % of the variability of the price is explained by the impermeability type. Plus, we observe that the price range is statistically different for Type 1 and Type 2. Type 1 is statistically cheaper than Type 2 :

Concerning the price dependency on Shape :

p <- plot_ly(type = 'box') %>%  add_boxplot(y = dataset$Price , x = dataset$Shape, jitter = 0.3, pointpos = -1.8, boxpoints = 'all',
              marker = list(color = 'rgb(7,40,89)'),
              line = list(color = 'rgb(7,40,89)'),
              name = "Price box") %>%  layout( title = 'Price versus Shape Boxplot',  yaxis = list(title = 'Price'))
p
Can't display both discrete & non-discrete data on same axisCan't display both discrete & non-discrete data on same axis

fit_price_shape <- lm(Price~ Shape, data=dataset)
summary(fit_price_shape)

Call:
lm(formula = Price ~ Shape, data = dataset)

Residuals:
    Min      1Q  Median      3Q     Max 
-11.098  -3.850  -1.025   3.055  25.587 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept)   14.2006     0.5406  26.267  < 2e-16 ***
ShapeShape 2   8.1403     1.0782   7.550 1.75e-12 ***
ShapeShape 3   1.4510     2.2777   0.637  0.52485    
ShapeShape 4   7.4393     2.2777   3.266  0.00129 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 6.258 on 191 degrees of freedom
Multiple R-squared:  0.2475,    Adjusted R-squared:  0.2357 
F-statistic: 20.94 on 3 and 191 DF,  p-value: 9.008e-12

In this case, it’s hard to notice a dependence between price and shape. Only 24 % of the variability of the price is explained by the Shape type. However, there is some insights :

Concerning the price dependency on Suppliers :

p <- plot_ly(type = 'box') %>%  add_boxplot(y = dataset$Price , x = dataset$Supplier, jitter = 0.3, pointpos = -1.8, boxpoints = 'all',
              marker = list(color = 'rgb(7,40,89)'),
              line = list(color = 'rgb(7,40,89)'),
              name = "Price box") %>%  layout( title = 'Price versus Impermeability Boxplot',  yaxis = list(title = 'Price'))
p
Can't display both discrete & non-discrete data on same axisCan't display both discrete & non-discrete data on same axis

fit_price_supplier <- lm(Price~ Supplier, data=dataset)
summary(fit_price_supplier)

Call:
lm(formula = Price ~ Supplier, data = dataset)

Residuals:
    Min      1Q  Median      3Q     Max 
-11.431  -4.491  -1.847   2.873  30.349 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)          18.029      1.285  14.033   <2e-16 ***
SupplierSupplier B   -1.768      1.411  -1.252    0.212    
SupplierSupplier C   -3.140      2.303  -1.363    0.174    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 7.153 on 192 degrees of freedom
Multiple R-squared:  0.01174,   Adjusted R-squared:  0.001449 
F-statistic: 1.141 on 2 and 192 DF,  p-value: 0.3217

There is no dependency on the price. Let’s study the prices in more details :

PriceComp_avg <- dataset %>% select(Supplier,Price) %>% group_by(Supplier) %>% summarise(Average_Price = mean(Price)) 
head(PriceComp_avg)
PriceComp_min <- dataset %>% select(Supplier,Price) %>% group_by(Supplier) %>% summarise(Minimum_Price = min(Price))
head(PriceComp_min)
PriceComp_avg_price_per_weight <- PriceComp_min <- dataset %>% select(Supplier,Price,weight) %>% group_by(Supplier) %>% summarise(Price_per_weight = mean(Price)/mean(weight))
head(PriceComp_avg_price_per_weight)

In terms of average price, the supplier C is the less expensive. In terms of absolute price, the supplier B is the less expensive. However, Supplier B is also the supplier which has the highest absolute price. *In terms of average price / weight Supplier A has is the less expensive.

QUESTION 3 : Identifying outliers

One important point in exploratory data analysis consists in identifying potential outliers. Let’s identify this outliers given different features. For Mature.Volume variable :

d <- density(dataset$Mature.Volume)
hist(dataset$Mature.Volume, breaks=40, probability = TRUE, main = "Mature Volume distribution",
     xlab = "Mature Volume")
lines(d, col = "red")

We can clearly notice here an outlier. We can now remove it

dataset <- dataset %>% filter ( Mature.Volume < 600000 )  

Let’s verify the data now :

d <- density(dataset$Mature.Volume)
hist(dataset$Mature.Volume, breaks=40, probability = TRUE,main = "Mature Volume distribution",
     xlab = "Mature Volume")
lines(d, col = "red")

After studying the other features distribution, we notice that there is no other outliers. Plus, it seems that every numerical feature have the same trend structure. Please find below the other distributions:

d <- density(dataset$Diameter)
hist(dataset$Diameter, breaks=40, probability = TRUE, main = "Diameter distribution",
     xlab = "Mature Volume")
lines(d, col = "red")

d <- density(dataset$weight)
hist(dataset$weight, breaks=40, probability = TRUE, main = "Mature Volume distribution",
     xlab = "Distribution")
lines(d, col = "red")

d <- density(dataset$nb.of.pieces)
hist(dataset$nb.of.pieces, breaks=40, probability = TRUE, main = "Nb of pieces distribution",
     xlab = "Nb of pieces")
lines(d, col = "red")

d <- density(dataset$Length)
hist(dataset$Length, breaks=40, probability = TRUE, main = "Lenght distribution",
     xlab = "Lenght")
lines(d, col = "red")

QUESTION 4/5/6/7 : PCA and correlation matrix

Now we will perform a PCA on the data. A PCA will allows us to fin a low-dimensional reprensation of the data that captures the “essense” of the raw data. Plus, a PCA will allows us denoise the data. This preprocessing and data exploration helps us to better understand/visualise the relations between the differents features and observations and to prepare the data for the prediction process. PCA deals with continuous variables but categorical variables are the projection of the categories at the barycentre of the observations which take the categories.

As we want to predict the price and we have Supplier, Shape, Impermeability and Finishing as qualitative variables, we will consider this last ones as illustrative. Let’s now process the PCA :

res.pca <- PCA(dataset, quali.sup=c(1,5,6,7,9), quanti.sup = 10, scale = TRUE)

summary(res.pca, nbelements = 10)

Call:
PCA(X = dataset, scale.unit = TRUE, quanti.sup = 10, quali.sup = c(1,  
     5, 6, 7, 9)) 


Eigenvalues
                       Dim.1   Dim.2   Dim.3   Dim.4   Dim.5
Variance               3.107   1.067   0.777   0.049   0.000
% of var.             62.142  21.338  15.537   0.976   0.006
Cumulative % of var.  62.142  83.481  99.018  99.994 100.000

Individuals (the 10 first)
                  Dist    Dim.1    ctr   cos2    Dim.2    ctr   cos2    Dim.3    ctr   cos2  
1             |  4.259 |  4.026  2.731  0.894 | -1.247  0.763  0.086 | -0.008  0.000  0.000 |
2             |  4.740 |  4.674  3.681  0.972 | -0.633  0.196  0.018 |  0.466  0.146  0.010 |
3             |  4.739 |  4.662  3.662  0.968 | -0.671  0.221  0.020 |  0.518  0.181  0.012 |
4             |  0.966 |  0.050  0.000  0.003 |  0.451  0.100  0.218 | -0.796  0.427  0.680 |
5             |  1.644 | -0.771  0.100  0.220 | -0.314  0.048  0.036 |  1.408  1.337  0.734 |
6             |  0.802 | -0.507  0.043  0.399 |  0.577  0.163  0.518 |  0.178  0.021  0.049 |
7             |  1.123 |  0.253  0.011  0.051 | -0.185  0.017  0.027 | -1.070  0.772  0.908 |
8             |  1.145 |  0.605  0.062  0.279 |  0.959  0.452  0.702 | -0.154  0.016  0.018 |
9             |  1.153 |  0.622  0.065  0.291 |  0.959  0.451  0.692 | -0.149  0.015  0.017 |
10            |  1.165 |  0.647  0.071  0.309 |  0.958  0.450  0.676 | -0.142  0.014  0.015 |

Variables
                 Dim.1    ctr   cos2    Dim.2    ctr   cos2    Dim.3    ctr   cos2  
Diameter      |  0.985 31.233  0.970 | -0.025  0.061  0.001 |  0.144  2.674  0.021 |
weight        |  0.977 30.750  0.955 | -0.029  0.077  0.001 |  0.103  1.371  0.011 |
nb.of.pieces  | -0.202  1.309  0.041 |  0.843 66.557  0.710 |  0.499 32.076  0.249 |
Mature.Volume | -0.412  5.458  0.170 | -0.596 33.258  0.355 |  0.690 61.213  0.476 |
Length        |  0.985 31.250  0.971 | -0.023  0.048  0.001 |  0.144  2.666  0.021 |

Supplementary continuous variable
                Dim.1  cos2   Dim.2  cos2   Dim.3  cos2  
Price         | 0.796 0.634 | 0.171 0.029 | 0.131 0.017 |

Supplementary categories (the 10 first)
                  Dist    Dim.1   cos2 v.test    Dim.2   cos2 v.test    Dim.3   cos2 v.test  
Supplier A    |  0.591 |  0.548  0.859  1.813 | -0.055  0.009 -0.308 | -0.214  0.131 -1.416 |
Supplier B    |  0.144 | -0.065  0.206 -0.949 | -0.126  0.759 -3.109 | -0.027  0.035 -0.782 |
Supplier C    |  1.675 | -0.444  0.070 -0.976 |  1.441  0.739  5.407 |  0.728  0.189  3.203 |
Shape 1       |  0.496 | -0.426  0.736 -4.859 | -0.138  0.077 -2.687 | -0.214  0.186 -4.888 |
Shape 2       |  1.530 |  1.427  0.870  6.196 |  0.394  0.066  2.921 |  0.383  0.063  3.325 |
Shape 3       |  0.668 | -0.560  0.703 -0.915 | -0.332  0.248 -0.927 |  0.060  0.008  0.195 |
Shape 4       |  1.427 | -0.552  0.150 -0.902 |  0.356  0.062  0.992 |  1.265  0.786  4.138 |
Type 1        |  0.450 | -0.450  1.000 -9.517 | -0.002  0.000 -0.066 | -0.009  0.000 -0.389 |
Type 2        |  3.290 |  3.289  1.000  9.517 |  0.013  0.000  0.066 |  0.067  0.000  0.389 |
Hot Printing  |  0.354 | -0.286  0.653 -1.551 | -0.038  0.011 -0.349 |  0.192  0.295  2.083 |
fviz_pca_ind(res.pca)

fviz_pca_ind(res.pca, col.ind="cos2", label=c("quali"), geom = "point") + scale_color_gradient2(low="lightblue", mid="blue", high="darkblue", midpoint=0.6)+ theme_minimal() 

Before commenting this graphs, let’s compute also the correlation matrix :

#Scaling the variables: 
X <- scale(as.matrix(dataset %>% select(-c(1,5,6,7,9,10))))
#Plotting the correlation matrix: 
as.data.frame(cov(X))

The variable factor map shows us that :

This correlations are well explained in the covariance matrix. The cells that correspond to a combinaison of highly correlated features have a cov higher than 0.9 and the cells that correspond to a combinaison of uncorrelated (orthogonal) features have a cov lower than 0.2 - 0.3.

plot(res.pca$eig[,3], type="l", ylab = "Cumulative percentage of inertia", xlab = "Nb of synthetic vectors")

QUESTION 8 : Synthetic variables

The R object with the two principal components which are the synthetic variables the most correlated to all the variables is the two eigen vectors of the PCA linked to the two highest eigenvalues.

as.data.frame(res.pca$var$coord[,1:2])

QUESTION 9

PCA is often used as a pre-processing step before applying a clustering algorithm. In fact, we often perform the CAH or the k-means on the \(k\) principal components to denoise the data. In this setting \(k\) is choosen as large since we do not want to loose any information, but want to discard the last components that can be considered as noise. Consequently, we keep the number of dimensions \(k\) such that we reach 95% of the inertia in PCA. In our case we have \(k=3\) (cf last graph)

QUESTION 10

Let’s now perform a kmeans algorithm on the selected k principal components of PCA.

# We keep the 3 first components of the PCA
dat <- res.pca$ind$coord[,1:3]
#Performing the clustering
clus <- kmeans(dat, 3, nstart = 20)
#Visualizing the clusters 
plot(dat, col = clus$cluster, pch = 19, frame = FALSE,  main = "K-means with k = 3")
points(clus$centers, col = 1:4, pch = 8, cex = 3)

# Visualizing the total within sum of squares and using "méthode du coude"
fviz_nbclust(dat, kmeans, method = "wss") + geom_vline(xintercept = 3, linetype = 2)

Using “methode du coude” we find that the optimal number of cluster is 3.

QUESTION 11

#Performing a PCA on the 3 principal compenents
res.pca3 <- PCA(dataset, quali.sup=c(1,5,6,7,9), quanti.sup = 10, scale = TRUE, ncp = 3)

# Performing the AHC on the 3 principal components of the PCA
res.hcpc3 <- HCPC(res.pca3, nb.clust = -1)

plot(res.hcpc3$call$t$within[1:14]) 

QUESTION 12 : Cluster description

as.data.frame(res.hcpc3$desc.var$quanti.var)

We notice here that all our quantitative variables describe well our 3 clusters. The 3 variables that describe very well our 3 clusters (low p-value) are Lenght, Diameter and weight. However, this is linked with our PCA. In fact, this last three variables are very representative of the first dimension of the PCA and then describe well our data. Given the fact that we performed the hcpc of the PCA data, it’s normal to find that these three variables describe well our clusters.

For the qualitative variables, we perform a khi2 test:

res.hcpc3$desc.var$test.chi2
                    p.value df
Impermeability 5.318642e-18  2
Raw.Material   5.226547e-17  4
Shape          5.626207e-06  6
Supplier       4.102258e-02  4

The variable Impermeability seems to be the most related to the partitioning (lowest p-value)

For the quantitative variables :

res.hcpc3$desc.var$quanti$`1`
                 v.test Mean in category Overall mean sd in category   Overall sd      p.value
Mature.Volume 11.942982     2.431183e+05 82206.026178   67166.762125 9.103190e+04 7.064414e-33
Diameter      -3.255425     8.214269e-01     1.294639       0.254233 9.821218e-01 1.132228e-03
Length        -3.297003     6.491733e+00    10.329589       2.056760 7.864783e+00 9.772253e-04
weight        -3.536244     1.100262e+00     1.714121       0.315574 1.172854e+00 4.058595e-04
nb.of.pieces  -3.780986     3.324324e+00     4.115183       1.274576 1.413225e+00 1.562083e-04
Price         -3.857939     1.245686e+01    16.552332       4.115901 7.172431e+00 1.143473e-04

The Cluster 1 is well defined by all the quantitative variables and more specifically by Mature.Volume. We can guess this result because the cluster 1 ( black points) is situated on the same axe than the Mature.Volume vector on the previous PCA.

res.hcpc3$desc.var$category$`1`
                        Cla/Mod    Mod/Cla    Global      p.value    v.test
Raw.Material=PP       25.000000  97.297297 75.392670 0.0001368886  3.813724
Impermeability=Type 1 22.023810 100.000000 87.958115 0.0049829303  2.808135
Supplier=Supplier C    0.000000   0.000000  7.329843 0.0434829294 -2.019041
Raw.Material=PS        3.846154   2.702703 13.612565 0.0222292828 -2.286427
Raw.Material=ABS       0.000000   0.000000 10.994764 0.0081544536 -2.645607
Impermeability=Type 2  0.000000   0.000000 12.041885 0.0049829303 -2.808135
Shape=Shape 2          2.222222   2.702703 23.560209 0.0002330416 -3.680210

On the same vein, \(Raw.Material=PP\) and \(Shape=Shape 2\) are defining well the \(cluster 1\) given the \(p-value\).

It is also interesting to illustrate the cluster 1 by computing its paragons

res.hcpc3$desc.ind$para$`1`
       94        95       142        74       144 
0.3280604 0.3376965 0.4205536 0.4265863 0.5886435 

The product 94,95,142,74,144 are closest to the centre of the cluster 1 centroid and then, are representative of this cluster.

QUESTION 13

We didn’t choose \(k = 2\) because in this configuration, the two first eigen vectors describe less than 95% of the data. We didn’t choose \(k=4\) because it’s not necessary given the fact that the configuration \(k=3\) is describing already 95% of our data.

A strategy to assess the stability of the approach is to do the same study that we did but for the hcpc with 2 and 4 compenent. Then, we will be able to compare the different p-values for each configuration and see going form 2 to 3 or from 3 to 4 have a significant impact on the results of our clustering. After doing this study we noticed that we have the same p-value results for \(k=3\) and \(k=4\). Plus, we noticed that we have the p-value results are in average better for \(k=3\) than \(k=2\). This insights explain us that 3 components is the best compromise and choice. On the same vein, we noticed that there a no difference in terms of cluster description for a clustering obtained on k components or on the initial data.

QUESTION 14

The methodology that we have used to describe clusters can also be used to describe a categorical variable, for instance the supplier.

catdes(dataset, num.var= 1) 
Chi-squared approximation may be incorrectChi-squared approximation may be incorrectChi-squared approximation may be incorrectChi-squared approximation may be incorrect
$test.chi2
                    p.value df
Raw.Material   9.049049e-05  4
Impermeability 1.088731e-02  2

$category
$category$`Supplier A`
                       Cla/Mod  Mod/Cla   Global      p.value    v.test
Raw.Material=PS       42.30769 37.93103 13.61257 0.0002998155  3.615459
Impermeability=Type 2 34.78261 27.58621 12.04188 0.0130149176  2.483361
Shape=Shape 2         26.66667 41.37931 23.56021 0.0213728107  2.301333
Raw.Material=ABS       0.00000  0.00000 10.99476 0.0254288561 -2.234825
Impermeability=Type 1 12.50000 72.41379 87.95812 0.0130149176 -2.483361

$category$`Supplier B`
                   Cla/Mod  Mod/Cla   Global     p.value    v.test
Raw.Material=ABS 100.00000 14.18919 10.99476 0.003330616  2.935453
Raw.Material=PS   57.69231 10.13514 13.61257 0.015928453 -2.410551
Shape=Shape 2     60.00000 18.24324 23.56021 0.002374481 -3.038894

$category$`Supplier C`
                 Cla/Mod Mod/Cla   Global    p.value   v.test
Raw.Material=PP 9.722222     100 75.39267 0.01626019 2.403023


$quanti.var
                  Eta2      P-value
nb.of.pieces 0.2137072 1.530822e-10

$quanti
$quanti$`Supplier A`
NULL

$quanti$`Supplier B`
                v.test Mean in category Overall mean sd in category Overall sd     p.value
nb.of.pieces -2.817845         3.959459     4.115183       1.240523   1.413225 0.004834708

$quanti$`Supplier C`
               v.test Mean in category Overall mean sd in category Overall sd      p.value
nb.of.pieces 6.345875         6.428571     4.115183       1.720228   1.413225 2.211654e-10


attr(,"class")
[1] "catdes" "list " 

We will describe here the Supplier categorical variable but the method is the same for the others variables. Using the results of catdes, we can interpret the different suppliers as classes. We notice first that Raw.Material and Impermeability describe well the “supplier clustering”. If we deep dive into the different suppliers insights we can for example notice that the Supplier A is characterized by (in order of importance) : Raw.Material=PS; Impermeability=Type 2,1 ;Shape=Shape 2 and Raw.Material=ABS. This variables best describe the Supplier A. Then, we see that this data is very insightfull. In fact, doing a catdes on supplier for example allows the user to do a benchermarking of the different suppliers.

QUESTION 15

To simultaneously take into account quantitative and categorical variables in the clustering we will use the HCPC function on the results of the FAMD ones. FAMD stands for Factorial Analysis of Mixed Data and is a PCA dedicated to mixed data.

res.famd = FAMD(dataset, ncp = 10, sup.var = c(10) )

res.famd$eig
        eigenvalue percentage of variance cumulative percentage of variance
comp 1   4.6069336              32.906669                          32.90667
comp 2   1.6700311              11.928794                          44.83546
comp 3   1.4684324              10.488803                          55.32426
comp 4   1.2771099               9.122213                          64.44648
comp 5   1.0019966               7.157119                          71.60360
comp 6   0.8935056               6.382183                          77.98578
comp 7   0.7712154               5.508681                          83.49446
comp 8   0.6765127               4.832233                          88.32669
comp 9   0.6046173               4.318695                          92.64539
comp 10  0.4570087               3.264348                          95.90974

This FAMD analysis has many impacts on the different results. First, the overall correlation between variables decrease (in fact, FAMD analysis takes into account more variables). We have to take 10 components (against 3 for the PCA) to reach 95% in terms of cumulative variance. Moreover, this analysis show us that the Price is uncorrelated with Supplier, Finishing, nb.of.pieces and Mature.Volume. However, Impermeability and Raw material directly influence the price of the product. Shape is partially correlated with the price.

res.hcpc <- HCPC(res.famd, nb.clust = -1, graph =  FALSE)
Chi-squared approximation may be incorrectChi-squared approximation may be incorrectChi-squared approximation may be incorrectChi-squared approximation may be incorrectChi-squared approximation may be incorrect
plot.HCPC(res.hcpc, choice = "map", draw.tree = FALSE, select = "drawn", title = '')

We see here that adding more variables in the analysis reduce the quality of the clustering in comparaison with the last one.

QUESTION 16

Let’s now perform a model to predict the Price :

library(gbm)
index_test <- sample(x=1:nrow(dataset), size=floor(0.3*nrow(dataset)))
testing_data_set <- dataset[index_test,]
training_data_set <- dataset[-index_test,]
fit <- lm(Price ~ Diameter + Length + weight + Raw.Material + Impermeability + Shape, data= training_data_set)
summary(fit)

Call:
lm(formula = Price ~ Diameter + Length + weight + Raw.Material + 
    Impermeability + Shape, data = training_data_set)

Residuals:
     Min       1Q   Median       3Q      Max 
-13.9119  -2.0775  -0.4518   1.9397   8.3672 

Coefficients:
                     Estimate Std. Error t value Pr(>|t|)    
(Intercept)          10.12367    1.25116   8.091 4.61e-13 ***
Diameter              8.71035   13.83976   0.629  0.53026    
Length               -0.59981    1.72778  -0.347  0.72906    
weight                0.61077    1.05438   0.579  0.56346    
Raw.MaterialPP       -0.77658    1.07197  -0.724  0.47016    
Raw.MaterialPS       -2.60612    1.41244  -1.845  0.06741 .  
ImpermeabilityType 2  7.41997    1.63463   4.539 1.32e-05 ***
ShapeShape 2         -0.02204    0.94265  -0.023  0.98139    
ShapeShape 3          0.48874    2.20456   0.222  0.82492    
ShapeShape 4          5.71577    1.72660   3.310  0.00122 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.69 on 124 degrees of freedom
Multiple R-squared:  0.776, Adjusted R-squared:  0.7598 
F-statistic: 47.74 on 9 and 124 DF,  p-value: < 2.2e-16
predicted_price <- predict.lm(fit,testing_data_set)
real_price <- testing_data_set$Price
Prediction <-  data_frame(predicted_price,real_price) %>% mutate(difference = abs(predicted_price-real_price))

The model generated here by keeping only variables that explain the price allows us to have an Adjusted R-squared of 75.98 % (for this specific training dataset generated)

p <- ggplot(Prediction, aes(x= real_price)) + geom_point(aes(y= predicted_price)) + geom_line(aes(y= real_price))
p

This graph shows us the points that are overestimated/underestimated for the testing dataset. It seems that in this example low prices are overestimated and high prices are underestimated.

quantile(Prediction$difference)
         0%         25%         50%         75%        100% 
 0.03753141  0.95380762  1.79597941  3.90414311 17.66292398 

For the performance, we can say that we estimate with less than 2 centime error 50% of the data and with less than 4 centime error 75% of the data.

The previous analysis can help us interpret this results because it will be possible to link an over/under estimated point with its position in the individual factor map and then understand that this point is not linked with the drivers of the price.

QUESTION 17

It’s not smart to do one model per supplier because :

QUESTION 18

These data contained missing values. One representative in the compagny suggests either to put 0 in the missing cells or to impute with the median of the variables. Comment. For the categorical variables with missing values, it is decided to create a new category “missing”. Comment.

The first idea will change all the structure of our data becaue it will change the correlation between the different variables. The second idea will not fix the problem and will change nothing because this method only renames the “NA” category to a “missing” category.

LS0tCnRpdGxlOiAiQ2FzZSBzdHVkeSAtIFNjcmV3IENhcHMgcHJpY2UgcHJlZGljdGlvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCmF1dGhvcjogIkFtaXIgQmVubWFoam91YiIKRGF0ZTogIjIwLTEwLTIwMTciCi0tLQoKCkEgY29tbW9uIGFwcHJvYWNoIHRvIGRldGVybWluZSB0aGUgY29zdCBvZiBwcm9kdWN0cyBpcyB0aGUgc2hvdWxkIGNvc3QgbWV0aG9kLiBJdCBjb25zaXN0cyBpbiBlc3RpbWF0aW5nIHdoYXQgYSBwcm9kdWN0IHNob3VsZCBjb3N0IGJhc2VkIG9uIG1hdGVyaWFscywgbGFib3IsIG92ZXJoZWFkLCBhbmQgcHJvZml0IG1hcmdpbi4gQWx0aG91Z2ggdGhpcyBzdHJhdGVneSBpcyB2ZXJ5IGFjY3VyYXRlLCBpdCBoYXMgdGhlIGRyYXdiYWNrIG9mIGJlaW5nIHRlZGlvdXMgYW5kIGl0IHJlcXVpcmVzIGV4cGVydCBrbm93bGVkZ2Ugb2YgaW5kdXN0cmlhbCB0ZWNobm9sb2dpZXMgYW5kIHByb2Nlc3Nlcy4gVG8gZ2V0IGEgcXVpY2sgZXN0aW1hdGlvbiwgaXQgaXMgcG9zc2libGUgdG8gYnVpbGQgYSBzdGF0aXN0aWNhbCBtb2RlbCB0byBwcmVkaWN0IHRoZSBwcmljZSBvZiBwcm9kdWN0cyBnaXZlbiB0aGVpciBjaGFyYWN0ZXJpc3RpY3MuIFdpdGggc3VjaCBhIG1vZGVsLCBpdCB3b3VsZCBubyBsb25nZXIgYmUgbmVjZXNzYXJ5IHRvIGJlIGFuIGV4cGVydCBvciB0byB3YWl0IHNldmVyYWwgZGF5cyB0byBhc3Nlc3MgdGhlIGltcGFjdCBvZiBhIGRlc2lnbiBtb2RpZmljYXRpb24sIGEgY2hhbmdlIGluIHN1cHBsaWVyIG9yIGEgY2hhbmdlIGluIHByb2R1Y3Rpb24gc2l0ZS4gQmVmb3JlIGJ1aWxpbmcgYSBtb2RlbCwgaXQgaXMgaW1wb3J0YW50IHRvIGV4cGxvcmUgdGhlIGRhdGEgd2hpY2ggaXMgdGhlIGFpbSBvZiB0aGlzIGNhc2Ugc3R1ZHkuIFRoaXMgc3R1ZHkgd2FzIGNvbW1pc3Npb25lZCBieSBhIGNvc21ldGljcyBjb21wYW55IHRoYXQgd2FudHMgdG8gZXN0aW1hdGUgdGhlIHByaWNlIG9mIFNjcmV3IENhcHMgb2Ygc2hhbXBvbyBib3R0bGVzLiAKCkxldCdzIGZpcnN0IGxvYWQgdGhlIGRhdGFiYXNlIHN0dWR5IGl0J3Mgc3RydWN0dXJlIGFuZCBsb2FkIHRoZSBkaWZmw6lyZW50IHBhY2thZ2VzLiAKCmBgYHtyfQojTG9hZGluZyB0aGUgZGlmZmVyZW50IHBhY2thZ2VzIGZvciB0aGlzIHN0dWR5IApsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoRmFjdG9NaW5lUikKbGlicmFyeShjbHVzdGVyKQpsaWJyYXJ5KGZwYykKbGlicmFyeShmYWN0b2V4dHJhKQpsaWJyYXJ5KEZhY3RvSW52ZXN0aWdhdGUpCmxpYnJhcnkocGxvdGx5KQoKYGBgCgoKKipRVUVTVElPTiAxIDogU2NyZXcgQ2FwcyBEYXRhc2V0KioKCk5vdywgd2UgbG9hZCB0aGUgZGF0YXNldCB1c2VkIGZvciB0aGlzIHN0dWR5OgoKCmBgYHtyfQojTG9hZGluZyB0aGUgZGF0YXNldApkYXRhc2V0IDwtIHJlYWQudGFibGUoIlNjcmV3Q2Fwcy5jc3YiLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiLCIsIGRlYyA9ICIuIiwgcm93Lm5hbWVzID0gMSkKCiNQcmludGluZyB0aGUgZGF0YXNldApoZWFkKGRhdGFzZXQpCgojVW5kZXJzdGFuZGluZyB0aGUgc3RydWN0dXJlCnByaW50KHBhc3RlMCgiREIgRGltZW5zaW9uczogIiwgZGltKGRhdGFzZXQpWzFdLCIgWCAiICwgZGltKGRhdGFzZXQpWzJdICkpCnN1bW1hcnkoZGF0YXNldCkKCgpgYGAKClRoZSBkYXRhIFNjcmV3Q2FwLmNzdiBjb250YWlucyAxOTUgbG90cyBvZiBzY3JldyBjYXBzIGRlc2NyaWJlZCBieSAxMSB2YXJpYWJsZXMuIERpYW1ldGVyLCB3ZWlnaHQsIGxlbmd0aCBhcmUgdGhlIHBoeXNpY2FsIGNoYXJhY3RlcmlzdGljcyBvZiB0aGUgY2FwOyBuYi5vZi5waWVjZXMgY29ycmVzcG9uZHMgdG8gdGhlIG51bWJlciBvZiBlbGVtZW50cyBvZiB0aGUgY2FwICh0aGUgcGljdHVyZSBhYm92ZSBjb3JyZXNwb25kcyB0byBhIGNhcCB3aXRoIDIgcGllY2VzOiB0aGUgdmFsdmUgKGNsYXBldCkgaXMgbWFkZSBvZiBhIGRpZmZlcmVudCBtYXRlcmlhbCk7IE1hdHVyZS52b2x1bWUgY29ycmVzcG9uZHMgdG8gdGhlIG51bWJlciBvZiBjYXBzIG9yZGVyZWQgYW5kIGJvdWdodCBieSB0aGUgY29tcGFnbnkgKG51bWJlciBpbiB0aGUgbG90KS4gQWxsIHRoZSBjYXRlZ29yaWNhbCBmZWF0dXJlcyBhcmUgRmFjdG9ycy4gVGhlIG90aGVyIGZlYXR1cmVzIGFyZSBudW1lcmljYWwuIAoKCgoqKlFVRVNUSU9OIDIgOiBVbml2YXJpYXRlIGFuZCBiaXZhcmlhdGUgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyoqCgoqUHJpY2UgZGlzdHJpYnV0aW9uKgoKYGBge3J9CmQgPC0gZGVuc2l0eShkYXRhc2V0JFByaWNlKQojUGxvdHRpbmcgdGhlIGhpc3RvZ3JhbQpoaXN0KGRhdGFzZXQkUHJpY2UsIGJyZWFrcz00MCwgcHJvYmFiaWxpdHkgPSBUUlVFLCBtYWluID0gIlByaWNlIGRpc3RyaWJ1dGlvbiIsCiAgICAgeGxhYiA9ICJQcmljZSIpCiNQbG90dGluZyB0aGUgZGVuc2l0eQpsaW5lcyhkLCBjb2wgPSAicmVkIikKYGBgCgpXZSBoYXZlIGhlcmUgYSBiaW1vZGFsIGRpc3RyaWJ1dGlvbiBhbmQgd2UgY2FuIGRlc2NyaWJlIGl0IGluIG1vcmUgZGV0YWlscyB3aXRoIHRoZSBxdWFudGlsZXM6IAoKYGBge3J9CgpwIDwtIHBsb3RfbHkodHlwZSA9ICdib3gnKSAlPiUgIGFkZF9ib3hwbG90KHkgPSBkYXRhc2V0JFByaWNlLCBqaXR0ZXIgPSAwLjMsIHBvaW50cG9zID0gLTEuOCwgYm94cG9pbnRzID0gJ2FsbCcsCiAgICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICdyZ2IoNyw0MCw4OSknKSwKICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICdyZ2IoNyw0MCw4OSknKSwKICAgICAgICAgICAgICBuYW1lID0gIkFsbCBQb2ludHMiKSAlPiUgIGxheW91dCggdGl0bGUgPSAnUHJpY2UgQm94cGxvdCcsICB5YXhpcyA9IGxpc3QodGl0bGUgPSAnUHJpY2UnKSkKcApgYGAKCgpVc2luZyB0aGlzIHBsb3RseSBib3hwbG90IHdlIG5vdGljZSB0aGF0IHdlIGhhdmUgMjUlIG9mIHRoZSBwcmljZXMgYmV0d2VlbiA2LjQ3NzQ1MSBhbmQgMTEuODA3MDIyLiA1MCUgYmV0d2VlbiA2LjQ3NzQ1MSBhbmQgMTQuMzg0NDEzIGFuZCA3NSUgYmV0d2VlbiA2LjQ3NzQ1MSBhbmQgMTguOTAyNDI5LiBUaGUgcmVtYW5pbmcgMjXigLAgYXJlIGRhdGEgbG9jYXRlZCBpbiBhIHdpZGUgcmFuZ2Ugb2YKcHJpY2VzIGJldHdlZW4gMTguOTAyNDI5IGFuZCA0Ni42MTAzNzIgCgoKKlByaWNlIGRlcGVuZGVuY3kgb24gbGVuZ3RoKgoKTGV0J3Mgc3R1ZHkgbm93IHRoZSBwcmljZSBkZXBlbmRlbmN5IG9uIGxlbmdodC4gCgpgYGB7cn0KcCA8LSBnZ3Bsb3QoZGF0YT1kYXRhc2V0LCBhZXMoeD0gTGVuZ3RoLCB5PSBQcmljZSkpICsgZ2VvbV9wb2ludChzaXplPTEpICsgZ2VvbV9zbW9vdGgobWV0aG9kPWxtKSArIGdndGl0bGUoIiBQcmljZSB2ZXJzdXMgbGVuZ2h0ICIpCmdncGxvdGx5KHApCgpmaXRfcHJpY2VfbGVuZ2h0IDwtIGxtKFByaWNlfkxlbmd0aCwgZGF0YT1kYXRhc2V0KQpzdW1tYXJ5KGZpdF9wcmljZV9sZW5naHQpCmBgYCAKCldlIGNhbiBvYnNlcnZlIGEgZGVwZW5kZW5jZS4gNjMuOSAlIG9mIHRoZSB2YXJpYWJpbGl0eSBvZiB0aGUgcHJpY2UgaXMgZXhwbGFpbmVkIGJ5IHRoZSBsZW5naHQuIAoKKlByaWNlIGRlcGVuZGVuY3kgb24gd2VpZ2h0KgoKTm93IHdlIHN0dWR5IHRoZSBwcmljZSBkZXBlbmRlbmN5IG9uIHdlaWdodC4gCgpgYGB7cn0KcCA8LSBnZ3Bsb3QoZGF0YT1kYXRhc2V0LCBhZXMoeD0gd2VpZ2h0LCB5PSBQcmljZSkpICsgZ2VvbV9wb2ludChzaXplPTEpICsgZ2VvbV9zbW9vdGgobWV0aG9kPWxtKSArIGdndGl0bGUoIiBQcmljZSB2ZXJzdXMgd2VpZ2h0ICIpCmdncGxvdGx5KHApCgpmaXRfcHJpY2Vfd2VpZ2h0IDwtIGxtKFByaWNlfndlaWdodCwgZGF0YT1kYXRhc2V0KQpzdW1tYXJ5KGZpdF9wcmljZV93ZWlnaHQpCmBgYCAKCgoKV2UgY2FuIGFsc28gb2JzZXJ2ZSBhIGRlcGVuZGVuY2UuIDYyICUgb2YgdGhlIHZhcmlhYmlsaXR5IG9mIHRoZSBwcmljZSBpcyBleHBsYWluZWQgYnkgdGhlIHdlaWdodC4gCgoKKlByaWNlIGRlcGVuZGVuY3kgb24gIEltcGVybWVhYmlsaXR5LCBTaGFwZSBhbmQgU3VwcGxpZXIqCgoKTm93IHdlIHdpbGwgZGlzY3VzcyB0aGUgcHJpY2UgZGVwZW5kZW5jeSBvbiBzb21lIGNhdGVnb3JpY2FsIGZlYXR1cmVzIHN1Y2ggYXMgSW1wZXJtZWFiaWxpdHksIFNoYXBlIGFuZCBTdXBwbGllci4gCgpgYGB7cn0KCnAgPC0gcGxvdF9seSh0eXBlID0gJ2JveCcpICU+JSAgYWRkX2JveHBsb3QoeSA9IGRhdGFzZXQkUHJpY2UgLCB4ID0gZGF0YXNldCRJbXBlcm1lYWJpbGl0eSwgaml0dGVyID0gMC4zLCBwb2ludHBvcyA9IC0xLjgsIGJveHBvaW50cyA9ICdhbGwnLAogICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAncmdiKDcsNDAsODkpJyksCiAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAncmdiKDcsNDAsODkpJyksCiAgICAgICAgICAgICAgbmFtZSA9ICJQcmljZSBib3giKSAlPiUgIGxheW91dCggdGl0bGUgPSAnUHJpY2UgdmVyc3VzIEltcGVybWVhYmlsaXR5IEJveHBsb3QnLCAgeWF4aXMgPSBsaXN0KHRpdGxlID0gJ1ByaWNlJykpCnAKCmZpdF9wcmljZV9pbXBlcm1lYWJpbGl0eSA8LSBsbShQcmljZX4gSW1wZXJtZWFiaWxpdHksIGRhdGE9ZGF0YXNldCkKc3VtbWFyeShmaXRfcHJpY2VfaW1wZXJtZWFiaWxpdHkpCgoKYGBgIAoKVGhlIGJveHBsb3Qgc2hvdyB1cyB0aGF0IGVhY2ggaW1wZXJtZWFiaWxpdHkgdHlwZSBnYXRoZXIgYSB3aWRlIHJhbmdlIG9mIHByaWNlcyA6IAoKKiBUeXBlIDEgOiBmcm9tIDEuNiB0byAzOS43CiogVHlwZSAyIDogZnJvbSAxMi44IHRvIDQ2LjYKCkhvd2V2ZXIsIHdlIG5vdGljZSAgYSBkZXBlbmRlbmNlLiA0MyAlIG9mIHRoZSB2YXJpYWJpbGl0eSBvZiB0aGUgcHJpY2UgaXMgZXhwbGFpbmVkIGJ5IHRoZSBpbXBlcm1lYWJpbGl0eSB0eXBlLiBQbHVzLCB3ZSBvYnNlcnZlIHRoYXQgIHRoZSBwcmljZSByYW5nZSBpcyBzdGF0aXN0aWNhbGx5IGRpZmZlcmVudCBmb3IgVHlwZSAxIGFuZCBUeXBlIDIuIFR5cGUgMSBpcyBzdGF0aXN0aWNhbGx5IGNoZWFwZXIgdGhhbiBUeXBlIDIgOiAKCiogVHlwZSAxIDogNTAlIG9mIHRoZSBkYXRhIGJldHdlZW4gJHFfezF9ID0gMTEkLjY5IGFuZCAkcV97M30gPSAxNyQKKiBUeXBlIDIgOiA1MCUgb2YgdGhlIGRhdGEgYmV0d2VlbiAkcV97MX0gPSAyNi41JCBhbmQgJHFfezN9ID0gMzQuMSQKCgpDb25jZXJuaW5nIHRoZSBwcmljZSBkZXBlbmRlbmN5IG9uIFNoYXBlIDogCgoKCmBgYHtyfQoKCnAgPC0gcGxvdF9seSh0eXBlID0gJ2JveCcpICU+JSAgYWRkX2JveHBsb3QoeSA9IGRhdGFzZXQkUHJpY2UgLCB4ID0gZGF0YXNldCRTaGFwZSwgaml0dGVyID0gMC4zLCBwb2ludHBvcyA9IC0xLjgsIGJveHBvaW50cyA9ICdhbGwnLAogICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSAncmdiKDcsNDAsODkpJyksCiAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAncmdiKDcsNDAsODkpJyksCiAgICAgICAgICAgICAgbmFtZSA9ICJQcmljZSBib3giKSAlPiUgIGxheW91dCggdGl0bGUgPSAnUHJpY2UgdmVyc3VzIFNoYXBlIEJveHBsb3QnLCAgeWF4aXMgPSBsaXN0KHRpdGxlID0gJ1ByaWNlJykpCnAKCmZpdF9wcmljZV9zaGFwZSA8LSBsbShQcmljZX4gU2hhcGUsIGRhdGE9ZGF0YXNldCkKc3VtbWFyeShmaXRfcHJpY2Vfc2hhcGUpCgpgYGAKCgpJbiB0aGlzIGNhc2UsIGl0J3MgaGFyZCB0byBub3RpY2UgIGEgZGVwZW5kZW5jZSBiZXR3ZWVuIHByaWNlIGFuZCBzaGFwZS4gT25seSAyNCAlIG9mIHRoZSB2YXJpYWJpbGl0eSBvZiB0aGUgcHJpY2UgaXMgZXhwbGFpbmVkIGJ5IHRoZSBTaGFwZSB0eXBlLiBIb3dldmVyLCB0aGVyZSBpcyBzb21lIGluc2lnaHRzICA6CgoqIENvbXBhcmluZyBTaGFwZSAxIGFuZCBTaGFwZSAyIHdlIG5vdGljZSB0aGF0IHNoYXBlIDEgcHJpY2VzIGFyZSBtb3JlIGdhdGhlcmVkIGludG8gYSBzbWFsbCBzdGF0aXN0aWNhbGwgaW50ZXJ2YWwgKCAkcV97Mn0gPSAxMS4xJCAsICRxX3szfSA9MTYuMSAkKSBpbiBjb21wYXJhaXNvbiB3aXRoIHNoYXBlIDIgcHJpY2UgZGF0YSAoICRxX3syfSA9IDE0JCAsICRxX3szfSA9MjguOCAkKQoqIFRoZXJlIGlzbid0IG1hbnkgcHJvZHVjdHMgZm9yIHNoYXBlIDMgYW5kIFNoYXBlIDQgaW4gY29tcGFyYWlzb24gd2l0aCB0aGUgdHdvIGZpcnN0IHNoYXBlcy4gSG93ZXZlciwgd2UgY2FuIHNlZSB0aGF0IHRoZSBwcmljZXMgZm9yIHNoYXBlIDMgYW5kIHNoYXBlIDQgYXJlIGxvY2F0ZWQgaW4gYSBzbWFsbCBpbnRlcnZhbCBpbiBjb21wYXJhaXNvbiB3aXRoIHRoZSB0d28gb3RoZXIgbGFzdCBzaGFwZXMuCgoKQ29uY2VybmluZyB0aGUgcHJpY2UgZGVwZW5kZW5jeSBvbiBTdXBwbGllcnMgOiAKCgpgYGB7cn0KCgpwIDwtIHBsb3RfbHkodHlwZSA9ICdib3gnKSAlPiUgIGFkZF9ib3hwbG90KHkgPSBkYXRhc2V0JFByaWNlICwgeCA9IGRhdGFzZXQkU3VwcGxpZXIsIGppdHRlciA9IDAuMywgcG9pbnRwb3MgPSAtMS44LCBib3hwb2ludHMgPSAnYWxsJywKICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gJ3JnYig3LDQwLDg5KScpLAogICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gJ3JnYig3LDQwLDg5KScpLAogICAgICAgICAgICAgIG5hbWUgPSAiUHJpY2UgYm94IikgJT4lICBsYXlvdXQoIHRpdGxlID0gJ1ByaWNlIHZlcnN1cyBJbXBlcm1lYWJpbGl0eSBCb3hwbG90JywgIHlheGlzID0gbGlzdCh0aXRsZSA9ICdQcmljZScpKQpwCgpmaXRfcHJpY2Vfc3VwcGxpZXIgPC0gbG0oUHJpY2V+IFN1cHBsaWVyLCBkYXRhPWRhdGFzZXQpCnN1bW1hcnkoZml0X3ByaWNlX3N1cHBsaWVyKQoKYGBgCgpUaGVyZSBpcyBubyBkZXBlbmRlbmN5IG9uIHRoZSBwcmljZS4gTGV0J3Mgc3R1ZHkgdGhlIHByaWNlcyBpbiBtb3JlIGRldGFpbHMgOiAKCgpgYGB7cn0KUHJpY2VDb21wX2F2ZyA8LSBkYXRhc2V0ICU+JSBzZWxlY3QoU3VwcGxpZXIsUHJpY2UpICU+JSBncm91cF9ieShTdXBwbGllcikgJT4lIHN1bW1hcmlzZShBdmVyYWdlX1ByaWNlID0gbWVhbihQcmljZSkpIAoKaGVhZChQcmljZUNvbXBfYXZnKQoKUHJpY2VDb21wX21pbiA8LSBkYXRhc2V0ICU+JSBzZWxlY3QoU3VwcGxpZXIsUHJpY2UpICU+JSBncm91cF9ieShTdXBwbGllcikgJT4lIHN1bW1hcmlzZShNaW5pbXVtX1ByaWNlID0gbWluKFByaWNlKSkKCmhlYWQoUHJpY2VDb21wX21pbikKClByaWNlQ29tcF9hdmdfcHJpY2VfcGVyX3dlaWdodCA8LSBQcmljZUNvbXBfbWluIDwtIGRhdGFzZXQgJT4lIHNlbGVjdChTdXBwbGllcixQcmljZSx3ZWlnaHQpICU+JSBncm91cF9ieShTdXBwbGllcikgJT4lIHN1bW1hcmlzZShQcmljZV9wZXJfd2VpZ2h0ID0gbWVhbihQcmljZSkvbWVhbih3ZWlnaHQpKQoKaGVhZChQcmljZUNvbXBfYXZnX3ByaWNlX3Blcl93ZWlnaHQpCgpgYGAKCipJbiB0ZXJtcyBvZiBhdmVyYWdlIHByaWNlLCB0aGUgc3VwcGxpZXIgQyBpcyB0aGUgbGVzcyBleHBlbnNpdmUuIAoqSW4gdGVybXMgb2YgYWJzb2x1dGUgcHJpY2UsIHRoZSBzdXBwbGllciBCIGlzIHRoZSBsZXNzIGV4cGVuc2l2ZS4gSG93ZXZlciwgU3VwcGxpZXIgQiBpcyBhbHNvIHRoZSBzdXBwbGllciB3aGljaCBoYXMgdGhlIGhpZ2hlc3QgYWJzb2x1dGUgcHJpY2UuCipJbiB0ZXJtcyBvZiBhdmVyYWdlIHByaWNlIC8gd2VpZ2h0IFN1cHBsaWVyIEEgaGFzIGlzIHRoZSBsZXNzIGV4cGVuc2l2ZS4KCioqUVVFU1RJT04gMyA6IElkZW50aWZ5aW5nIG91dGxpZXJzKioKCk9uZSBpbXBvcnRhbnQgcG9pbnQgaW4gZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyBjb25zaXN0cyBpbiBpZGVudGlmeWluZyBwb3RlbnRpYWwgb3V0bGllcnMuIApMZXQncyBpZGVudGlmeSB0aGlzIG91dGxpZXJzIGdpdmVuIGRpZmZlcmVudCBmZWF0dXJlcy4gRm9yIE1hdHVyZS5Wb2x1bWUgdmFyaWFibGUgOiAKCgpgYGB7cn0KCmQgPC0gZGVuc2l0eShkYXRhc2V0JE1hdHVyZS5Wb2x1bWUpCmhpc3QoZGF0YXNldCRNYXR1cmUuVm9sdW1lLCBicmVha3M9NDAsIHByb2JhYmlsaXR5ID0gVFJVRSwgbWFpbiA9ICJNYXR1cmUgVm9sdW1lIGRpc3RyaWJ1dGlvbiIsCiAgICAgeGxhYiA9ICJNYXR1cmUgVm9sdW1lIikKbGluZXMoZCwgY29sID0gInJlZCIpCgpgYGAKCgpXZSBjYW4gY2xlYXJseSBub3RpY2UgaGVyZSBhbiBvdXRsaWVyLiBXZSBjYW4gbm93IHJlbW92ZSBpdCAKCmBgYHtyfQpkYXRhc2V0IDwtIGRhdGFzZXQgJT4lIGZpbHRlciAoIE1hdHVyZS5Wb2x1bWUgPCA2MDAwMDAgKSAgCmBgYAoKTGV0J3MgdmVyaWZ5IHRoZSBkYXRhIG5vdyA6IAoKYGBge3J9CmQgPC0gZGVuc2l0eShkYXRhc2V0JE1hdHVyZS5Wb2x1bWUpCmhpc3QoZGF0YXNldCRNYXR1cmUuVm9sdW1lLCBicmVha3M9NDAsIHByb2JhYmlsaXR5ID0gVFJVRSxtYWluID0gIk1hdHVyZSBWb2x1bWUgZGlzdHJpYnV0aW9uIiwKICAgICB4bGFiID0gIk1hdHVyZSBWb2x1bWUiKQpsaW5lcyhkLCBjb2wgPSAicmVkIikKYGBgCgpBZnRlciBzdHVkeWluZyB0aGUgb3RoZXIgZmVhdHVyZXMgZGlzdHJpYnV0aW9uLCB3ZSBub3RpY2UgdGhhdCB0aGVyZSBpcyBubyBvdGhlciBvdXRsaWVycy4gUGx1cywgaXQgc2VlbXMgdGhhdCBldmVyeSBudW1lcmljYWwgZmVhdHVyZSBoYXZlIHRoZSBzYW1lIHRyZW5kIHN0cnVjdHVyZS4gUGxlYXNlIGZpbmQgYmVsb3cgdGhlIG90aGVyIGRpc3RyaWJ1dGlvbnM6CgpgYGB7cn0KZCA8LSBkZW5zaXR5KGRhdGFzZXQkRGlhbWV0ZXIpCmhpc3QoZGF0YXNldCREaWFtZXRlciwgYnJlYWtzPTQwLCBwcm9iYWJpbGl0eSA9IFRSVUUsIG1haW4gPSAiRGlhbWV0ZXIgZGlzdHJpYnV0aW9uIiwKICAgICB4bGFiID0gIk1hdHVyZSBWb2x1bWUiKQpsaW5lcyhkLCBjb2wgPSAicmVkIikKCgpgYGAKYGBge3J9CmQgPC0gZGVuc2l0eShkYXRhc2V0JHdlaWdodCkKaGlzdChkYXRhc2V0JHdlaWdodCwgYnJlYWtzPTQwLCBwcm9iYWJpbGl0eSA9IFRSVUUsIG1haW4gPSAiTWF0dXJlIFZvbHVtZSBkaXN0cmlidXRpb24iLAogICAgIHhsYWIgPSAiRGlzdHJpYnV0aW9uIikKbGluZXMoZCwgY29sID0gInJlZCIpCmBgYApgYGB7cn0KZCA8LSBkZW5zaXR5KGRhdGFzZXQkbmIub2YucGllY2VzKQpoaXN0KGRhdGFzZXQkbmIub2YucGllY2VzLCBicmVha3M9NDAsIHByb2JhYmlsaXR5ID0gVFJVRSwgbWFpbiA9ICJOYiBvZiBwaWVjZXMgZGlzdHJpYnV0aW9uIiwKICAgICB4bGFiID0gIk5iIG9mIHBpZWNlcyIpCmxpbmVzKGQsIGNvbCA9ICJyZWQiKQpgYGAKYGBge3J9CmQgPC0gZGVuc2l0eShkYXRhc2V0JExlbmd0aCkKaGlzdChkYXRhc2V0JExlbmd0aCwgYnJlYWtzPTQwLCBwcm9iYWJpbGl0eSA9IFRSVUUsIG1haW4gPSAiTGVuZ2h0IGRpc3RyaWJ1dGlvbiIsCiAgICAgeGxhYiA9ICJMZW5naHQiKQpsaW5lcyhkLCBjb2wgPSAicmVkIikKYGBgCgoKKipRVUVTVElPTiA0LzUvNi83ICA6IFBDQSBhbmQgY29ycmVsYXRpb24gbWF0cml4ICoqCgpOb3cgd2Ugd2lsbCBwZXJmb3JtIGEgUENBIG9uIHRoZSBkYXRhLiBBIFBDQSB3aWxsIGFsbG93cyB1cyB0byBmaW4gYSBsb3ctZGltZW5zaW9uYWwgcmVwcmVuc2F0aW9uIG9mIHRoZSBkYXRhIHRoYXQgCmNhcHR1cmVzIHRoZSAiZXNzZW5zZSIgb2YgdGhlIHJhdyBkYXRhLiBQbHVzLCBhIFBDQSB3aWxsIGFsbG93cyB1cyBkZW5vaXNlIHRoZSBkYXRhLiBUaGlzIHByZXByb2Nlc3NpbmcgYW5kIApkYXRhIGV4cGxvcmF0aW9uIGhlbHBzIHVzIHRvIGJldHRlciB1bmRlcnN0YW5kL3Zpc3VhbGlzZSB0aGUgcmVsYXRpb25zIGJldHdlZW4gdGhlIGRpZmZlcmVudHMgZmVhdHVyZXMgYW5kIG9ic2VydmF0aW9ucyBhbmQgdG8gcHJlcGFyZSB0aGUgZGF0YSBmb3IgdGhlIHByZWRpY3Rpb24gcHJvY2Vzcy4gUENBIGRlYWxzIHdpdGggY29udGludW91cyB2YXJpYWJsZXMgYnV0CmNhdGVnb3JpY2FsIHZhcmlhYmxlcyBhcmUgdGhlIHByb2plY3Rpb24gb2YgdGhlIGNhdGVnb3JpZXMgYXQgdGhlIGJhcnljZW50cmUgb2YgdGhlIG9ic2VydmF0aW9ucyB3aGljaCB0YWtlIHRoZSBjYXRlZ29yaWVzLiAKCkFzIHdlIHdhbnQgdG8gcHJlZGljdCB0aGUgcHJpY2UgYW5kIHdlIGhhdmUgU3VwcGxpZXIsIFNoYXBlLCBJbXBlcm1lYWJpbGl0eSBhbmQgRmluaXNoaW5nIGFzIHF1YWxpdGF0aXZlIHZhcmlhYmxlcywKd2Ugd2lsbCBjb25zaWRlciB0aGlzIGxhc3Qgb25lcyBhcyBpbGx1c3RyYXRpdmUuIExldCdzIG5vdyBwcm9jZXNzIHRoZSBQQ0EgOiAKCmBgYHtyfQpyZXMucGNhIDwtIFBDQShkYXRhc2V0LCBxdWFsaS5zdXA9YygxLDUsNiw3LDkpLCBxdWFudGkuc3VwID0gMTAsIHNjYWxlID0gVFJVRSkKc3VtbWFyeShyZXMucGNhLCBuYmVsZW1lbnRzID0gMTApCmZ2aXpfcGNhX2luZChyZXMucGNhKQpmdml6X3BjYV9pbmQocmVzLnBjYSwgY29sLmluZD0iY29zMiIsIGxhYmVsPWMoInF1YWxpIiksIGdlb20gPSAicG9pbnQiKSArIHNjYWxlX2NvbG9yX2dyYWRpZW50Mihsb3c9ImxpZ2h0Ymx1ZSIsIG1pZD0iYmx1ZSIsIGhpZ2g9ImRhcmtibHVlIiwgbWlkcG9pbnQ9MC42KSsgdGhlbWVfbWluaW1hbCgpIApgYGAKCkJlZm9yZSBjb21tZW50aW5nIHRoaXMgZ3JhcGhzLCBsZXQncyBjb21wdXRlIGFsc28gdGhlIGNvcnJlbGF0aW9uIG1hdHJpeCA6IAoKYGBge3J9CiNTY2FsaW5nIHRoZSB2YXJpYWJsZXM6IApYIDwtIHNjYWxlKGFzLm1hdHJpeChkYXRhc2V0ICU+JSBzZWxlY3QoLWMoMSw1LDYsNyw5LDEwKSkpKQojUGxvdHRpbmcgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeDogCmFzLmRhdGEuZnJhbWUoY292KFgpKQpgYGAKCgpUaGUgdmFyaWFibGUgZmFjdG9yIG1hcCBzaG93cyB1cyB0aGF0IDogCgoqIExlbmdodCwgd2VpZ2h0LCBwcmljZSBhbmQgZGlhbWV0ZXIgYXJlIHdlbGwgcHJvamVjdGVkIG9uIHRoZSAxc3QgZGltZW5zaW9uYWwgc3Vic3BhY2UgYW5kIGFsc28gY29ycmVsYXRlZCBiZXR3ZWVuCnRoZW0gcG9zaXRpdmVseS4gSW4gZmFjdCwgaXQncyBuYXR1cmFsIHRvIHNheSB0aGF0IHdoZW4gdGhlIGxlbmdodCBpbmNyZWFzZSBmb3IgZXhhbXBsZSB0aGVuIHRoZSB3ZWlnaHQsIFByaWNlIGFuZCBEaWFtZXRlciBpbmNyZWFzZSBhbHNvLiAKKiBNYXR1cmUgVm9sdW1lIGFuZCBOdW1iZXIgb2YgUGllY2VzIGFyZSB3ZWxsIHByb2plY3RlZCBvbiB0aGUgMm5kIGRpbWVuc2lvbmFsIHN1YnNwYWNlIGFuZCB0aGVuIGFyZSBub3QgcmVhbGx5IApjb3JyZWxhdGVkIHRvIExlbmdodCwgd2VpZ2h0LCBQcmljZSBhbmQgRGlhbWV0ZXIuIFBsdXMsIHdlIG5vdGljZSB0aGF0IE1hdHVyZSBWb2x1bWUgYW5kIE51bWJlciBvZiBQaWVjZXMgYXJlIG5lZ2F0aXZlbHkgY29ycmVsYXRlZCB3aGljaCBtZWFucyB0aGF0IHdoZW4gdGhlIG1vcmUgcGllY2VzIHdlIGhhdmUgZm9yIGEgcHJvZHVjdCB0aGUgbGVzcyB0aGUgY29tcGFnbnkgY29tbWFuZCB0aGlzIGtpbmQgb2YgcHJvZHVjdC4gCiogUHJpY2UgaXMgdW5jb3JlbGF0ZWQgd2l0aCBuYi5vZi5waWVjZXMKClRoaXMgY29ycmVsYXRpb25zIGFyZSB3ZWxsIGV4cGxhaW5lZCBpbiB0aGUgY292YXJpYW5jZSBtYXRyaXguIFRoZSBjZWxscyB0aGF0IGNvcnJlc3BvbmQgdG8gYSBjb21iaW5haXNvbiBvZgpoaWdobHkgY29ycmVsYXRlZCBmZWF0dXJlcyBoYXZlIGEgY292ICBoaWdoZXIgdGhhbiAwLjkgYW5kIHRoZSBjZWxscyB0aGF0IGNvcnJlc3BvbmQgdG8gYSBjb21iaW5haXNvbiBvZiAKdW5jb3JyZWxhdGVkIChvcnRob2dvbmFsKSBmZWF0dXJlcyBoYXZlIGEgY292IGxvd2VyIHRoYW4gMC4yIC0gMC4zLiAKCiogVGhlIFBDQSBmb2N1c2VzIG9uIHRoZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gdGhlIGNvbnRpbnVvdXMgdmFyaWFibGVzLiBJbiBmYWN0LCB0aGUgUENBIGNvbXB1dGUgdGhlIAp2ZWN0b3JzIHdoaWNoIGFyZSB0aGUgc3ludGhldGljIHZhcmlhYmxlcyB0aGUgbW9zdCBjb3JyZWxhdGVkIHRvIGFsbCB0aGUgY29udGludW91cyB2YXJpYWJsZXMuIFRoZW4sIGl0cyBwb3NzaWJsZSB0byBzdHVkeSB0aGUgcHJvamVjdGlvbiBvZiB0aGUgb2JzZXJ2YXRpb25zL2ZlYXR1cmVzIG9uIHRoaXMgdmVjdG9ycyBhbmQgZGlzY3VzcyB0aGUgbGluayBiZXR3ZWVuIHRoZW0uIFRoZSBpc3N1ZQppcyB0aGF0IHRoZSBQQ0EgZG9lcyBub3QgaGFuZGxlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB0byB0aGUgY29tcHV0YXRpb24gb2YgdGhlIHN5bnRoZXRpYyB2ZWN0b3JzLgoKKiBMZXQncyBub3cgZm9jdXMgb24gdGhlIGluZGl2aWR1YWwgZmFjdG9yIG1hcCA6IFRoZSBiYXJ5Y2VudHJlIHJlbGF0ZWQgdG8gJEltcGVybWVhYmlsaXR5ID0gVHlwZTIkIGFuZAokUmF3Lk1hdGVyaWFsID0gUFMkIGFyZSBuZWFyIHRvIHRoZSBmaXJzdCBzeW50aGV0aWMgYXhlIHdoaWNoIG1lYW5zIHRoYXQgdGhpcyB0d28gY2F0ZWdvcmllcyBUeXBlMiBhbmQgUFMgYXJlIGhpZ2hseSBjb3JyZWxhdGVkIHRvIHRoaXMgYXhlIGFuZCB0aGVuLCBnaXZlbiB0aGUgcHJldmlvdXMgYW5hbHlzaXMgdG8gTGVuZ2h0LCB3ZWlnaHQsIHByaWNlIGFuZCBkaWFtZXRlci4gSW4gZmFjdCwgZm9yIGluc3RhbmNlLCB3ZSBoYXZlIHNlZW4gdGhhdCBUeXBlIDIgaGF2ZSBhIGhpZ2hlciBwcmljZSBpbiBhdmVyYWdlIHRoYW4gVHlwZSAxLiBXZSBjYW4gc2F5IGFsc28gZm9yIGV4YW1wbGUKdGhhbiBhIFBTIHByb2R1Y3QgaXMgcmVsYXRlZCB0byBhIGhpZ2ggZGlhbWV0ZXIuIAoKCgpgYGB7cn0KcGxvdChyZXMucGNhJGVpZ1ssM10sIHR5cGU9ImwiLCB5bGFiID0gIkN1bXVsYXRpdmUgcGVyY2VudGFnZSBvZiBpbmVydGlhIiwgeGxhYiA9ICJOYiBvZiBzeW50aGV0aWMgdmVjdG9ycyIpCmBgYAoKCiogQ29uY2VybmluZyB0aGUgcG91cmNlbnRhZ2Ugb2YgaW5lcnRpYSwgdGhpcyBncmFwaCBzaG93IHVzIHRoYXQgd2UgY2FuIHN5bnRoZXRpc2UgbW9yZSB0aGFuIDk1JSBvZiB0aGUgdmFyaWFuY2Ugd2l0aCB0aGUgMyBmaXJzdCBzeW50aGV0aWMgdmVjdG9ycy4gCgoKKipRVUVTVElPTiA4ICA6IFN5bnRoZXRpYyB2YXJpYWJsZXMgKioKCgpUaGUgUiBvYmplY3Qgd2l0aCB0aGUgdHdvIHByaW5jaXBhbCBjb21wb25lbnRzIHdoaWNoIGFyZSB0aGUgc3ludGhldGljIHZhcmlhYmxlcyB0aGUgbW9zdCBjb3JyZWxhdGVkIHRvIGFsbCB0aGUgdmFyaWFibGVzIGlzIHRoZSB0d28gZWlnZW4gdmVjdG9ycyBvZiB0aGUgUENBIGxpbmtlZCB0byB0aGUgdHdvIGhpZ2hlc3QgZWlnZW52YWx1ZXMuIAoKYGBge3J9CmFzLmRhdGEuZnJhbWUocmVzLnBjYSR2YXIkY29vcmRbLDE6Ml0pCmBgYAoKCgoqKlFVRVNUSU9OIDkgKioKClBDQSBpcyBvZnRlbiB1c2VkIGFzIGEgcHJlLXByb2Nlc3Npbmcgc3RlcCBiZWZvcmUgYXBwbHlpbmcgYSBjbHVzdGVyaW5nIGFsZ29yaXRobS4gSW4gZmFjdCwgd2Ugb2Z0ZW4gcGVyZm9ybSB0aGUgQ0FIIG9yIHRoZSBrLW1lYW5zIG9uIHRoZSAkayQgcHJpbmNpcGFsIGNvbXBvbmVudHMgdG8gZGVub2lzZSB0aGUgZGF0YS4gSW4gdGhpcyBzZXR0aW5nICRrJCBpcyBjaG9vc2VuIGFzIGxhcmdlIHNpbmNlIHdlIGRvIG5vdCB3YW50IHRvIGxvb3NlIGFueSBpbmZvcm1hdGlvbiwgYnV0IHdhbnQgdG8gZGlzY2FyZCB0aGUgbGFzdCBjb21wb25lbnRzIHRoYXQgY2FuIGJlIGNvbnNpZGVyZWQgYXMgbm9pc2UuIENvbnNlcXVlbnRseSwgd2UgIGtlZXAgdGhlIG51bWJlciBvZiBkaW1lbnNpb25zICRrJCBzdWNoIHRoYXQgd2UgcmVhY2ggOTUlIG9mIHRoZSBpbmVydGlhIGluIFBDQS4gSW4gb3VyIGNhc2Ugd2UgaGF2ZSAkaz0zJCAoY2YgbGFzdCBncmFwaCkKCioqUVVFU1RJT04gMTAgKioKCkxldCdzIG5vdyBwZXJmb3JtIGEga21lYW5zIGFsZ29yaXRobSBvbiB0aGUgc2VsZWN0ZWQgayBwcmluY2lwYWwgY29tcG9uZW50cyBvZiBQQ0EuIAoKCmBgYHtyfQoKIyBXZSBrZWVwIHRoZSAzIGZpcnN0IGNvbXBvbmVudHMgb2YgdGhlIFBDQQpkYXQgPC0gcmVzLnBjYSRpbmQkY29vcmRbLDE6M10KI1BlcmZvcm1pbmcgdGhlIGNsdXN0ZXJpbmcKY2x1cyA8LSBrbWVhbnMoZGF0LCAzLCBuc3RhcnQgPSAyMCkKCiNWaXN1YWxpemluZyB0aGUgY2x1c3RlcnMgCgpwbG90KGRhdCwgY29sID0gY2x1cyRjbHVzdGVyLCBwY2ggPSAxOSwgZnJhbWUgPSBGQUxTRSwgIG1haW4gPSAiSy1tZWFucyB3aXRoIGsgPSAzIikKcG9pbnRzKGNsdXMkY2VudGVycywgY29sID0gMTo0LCBwY2ggPSA4LCBjZXggPSAzKQoKIyBWaXN1YWxpemluZyB0aGUgdG90YWwgd2l0aGluIHN1bSBvZiBzcXVhcmVzIGFuZCB1c2luZyAibcOpdGhvZGUgZHUgY291ZGUiCgpmdml6X25iY2x1c3QoZGF0LCBrbWVhbnMsIG1ldGhvZCA9ICJ3c3MiKSArIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDMsIGxpbmV0eXBlID0gMikKCmBgYAoKVXNpbmcgIm1ldGhvZGUgZHUgY291ZGUiIHdlIGZpbmQgdGhhdCB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlciBpcyAzLiAKCioqUVVFU1RJT04gMTEgKioKCmBgYHtyfQoKI1BlcmZvcm1pbmcgYSBQQ0Egb24gdGhlIDMgcHJpbmNpcGFsIGNvbXBlbmVudHMKcmVzLnBjYTMgPC0gUENBKGRhdGFzZXQsIHF1YWxpLnN1cD1jKDEsNSw2LDcsOSksIHF1YW50aS5zdXAgPSAxMCwgc2NhbGUgPSBUUlVFLCBuY3AgPSAzKQoKCgpgYGAKCmBgYHtyfQojIFBlcmZvcm1pbmcgdGhlIEFIQyBvbiB0aGUgMyBwcmluY2lwYWwgY29tcG9uZW50cyBvZiB0aGUgUENBCnJlcy5oY3BjMyA8LSBIQ1BDKHJlcy5wY2EzLCBuYi5jbHVzdCA9IC0xKQpwbG90KHJlcy5oY3BjMyRjYWxsJHQkd2l0aGluWzE6MTRdKSAKYGBgCgoqKlFVRVNUSU9OIDEyIDogQ2x1c3RlciBkZXNjcmlwdGlvbiAqKgoKCiogVGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIGNhbGN1bGF0ZWQgYnkgdGhlIGhjcGMgZnVuY3Rpb24gaXMgMy4gVGhpcyBmdW5jdGlvbiB1c2VkICJtZXRob2QgZHUgY291ZGUiIG9uICAgY2FsbCR0JHdpdGhpbi4gCgogCiogV2Ugbm93IGFpbSBhdCBkZXNjcmliaW5nIHRoZSBjbHVzdGVycy4gRmlyc3QsIGxldCdzIHN0dWR5IHRoZSB2YXJpYWJsZXMgd2hpY2ggYXJlIG1vc3QgaW1wb3J0YW50IGZvciB0aGUgcGFydGl0aW9uLiBUaGUgaGNwYyBmdW5jdGlvbiBjb21wdXRlIGEgZmlzaGVyIHRlc3QgdGhhdCBhbGxvd3MgdXMgdG8gZXZhbHVhdGUgdGhlIGxpbmsgYmV0d2VlbiBxdWFsaXRhdGl2ZSBhbmQgcXVhbnRpdGF0aXZlIHZhcmlhYmxlLiBIZXJlIDogdGhlIGNsdXN0ZXJzIGFuZCBvdXIgcXVhbnRpdGF0aXZlIHZhcmlhYmxlcy4gCgoKYGBge3J9CgoKYXMuZGF0YS5mcmFtZShyZXMuaGNwYzMkZGVzYy52YXIkcXVhbnRpLnZhcikKCmBgYAoKV2Ugbm90aWNlIGhlcmUgdGhhdCBhbGwgb3VyIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMgZGVzY3JpYmUgd2VsbCBvdXIgMyBjbHVzdGVycy4gVGhlIDMgdmFyaWFibGVzIHRoYXQgZGVzY3JpYmUgdmVyeSB3ZWxsIG91ciAzIGNsdXN0ZXJzIChsb3cgcC12YWx1ZSkgYXJlIExlbmdodCwgRGlhbWV0ZXIgYW5kIHdlaWdodC4gSG93ZXZlciwgdGhpcyBpcyBsaW5rZWQgd2l0aCBvdXIgUENBLiBJbiBmYWN0LCB0aGlzIGxhc3QgdGhyZWUgdmFyaWFibGVzIGFyZSB2ZXJ5IHJlcHJlc2VudGF0aXZlIG9mIHRoZSBmaXJzdCBkaW1lbnNpb24gb2YgdGhlIFBDQSBhbmQgdGhlbiBkZXNjcmliZSB3ZWxsIG91ciBkYXRhLiBHaXZlbiB0aGUgZmFjdCB0aGF0IHdlIHBlcmZvcm1lZCB0aGUgaGNwYyBvZiB0aGUgUENBIGRhdGEsIGl0J3Mgbm9ybWFsIHRvIGZpbmQgdGhhdCB0aGVzZSB0aHJlZSB2YXJpYWJsZXMgZGVzY3JpYmUgd2VsbCBvdXIgY2x1c3RlcnMuIAoKCkZvciB0aGUgcXVhbGl0YXRpdmUgdmFyaWFibGVzLCB3ZSBwZXJmb3JtIGEga2hpMiB0ZXN0OiAKCgpgYGB7ciB9CnJlcy5oY3BjMyRkZXNjLnZhciR0ZXN0LmNoaTIKYGBgCgoKVGhlIHZhcmlhYmxlIEltcGVybWVhYmlsaXR5IHNlZW1zIHRvIGJlIHRoZSBtb3N0IHJlbGF0ZWQgdG8gdGhlIHBhcnRpdGlvbmluZyAobG93ZXN0IHAtdmFsdWUpIAoKCiogTm93IHdlIHdhbnQgdG8gY2hhcmFjdGVyaXplIGNsdXN0ZXIgMSAoc2FtZSBtZXRob2RlIGZvciB0aGUgb3RoZXIgY2x1c3RlcnMpICBpbiB0ZXJtcyBvZiB2YXJpYWJsZXMgdGhhdCBiZXN0IGRlc2NyaWJlIHRoaXMgY2x1c3Rlci4gSW4gb3RoZXIgdGVybXMsIHdlIHdhbnQgdG8ga25vdyBmb3IgZWFjaCBxdWFudGl0YXRpdmUgdmFyaWFibGUgJHgkLCBpZiB0aGVyZSBpcyBhIHN0YXRpc3RpY2FsIGRpZmZlcmVuY2UgZm9yIHRoaXMgdmFyaWFibGUgYmV0d2VlbiBjbHVzdGVyICQxJCBhbmQgdGhlIHdob2xlIHBvcHVsYXRpb24gKHNpbXBsZSBzdGFuZGFyZCBHYXVzc2lhbiBkaXN0cmlidXRpb24gdGVzdCkKCkZvciB0aGUgcXVhbnRpdGF0aXZlIHZhcmlhYmxlcyA6IAoKYGBge3J9CnJlcy5oY3BjMyRkZXNjLnZhciRxdWFudGkkYDFgCmBgYAoKClRoZSBDbHVzdGVyIDEgaXMgd2VsbCBkZWZpbmVkIGJ5IGFsbCB0aGUgcXVhbnRpdGF0aXZlIHZhcmlhYmxlcyBhbmQgbW9yZSBzcGVjaWZpY2FsbHkgYnkgTWF0dXJlLlZvbHVtZS4gV2UgY2FuIGd1ZXNzIHRoaXMgcmVzdWx0IGJlY2F1c2UgdGhlIGNsdXN0ZXIgMSAoIGJsYWNrIHBvaW50cykgaXMgc2l0dWF0ZWQgb24gdGhlIHNhbWUgYXhlIHRoYW4gdGhlIE1hdHVyZS5Wb2x1bWUgdmVjdG9yIG9uIHRoZSBwcmV2aW91cyBQQ0EuIAoKCgoKCgpgYGB7cn0KCnJlcy5oY3BjMyRkZXNjLnZhciRjYXRlZ29yeSRgMWAKCmBgYAoKT24gdGhlIHNhbWUgdmVpbiwgJFJhdy5NYXRlcmlhbD1QUCQgYW5kICRTaGFwZT1TaGFwZSAyJCBhcmUgZGVmaW5pbmcgd2VsbCB0aGUgJGNsdXN0ZXIgMSQgZ2l2ZW4gdGhlICRwLXZhbHVlJC4KCkl0IGlzIGFsc28gaW50ZXJlc3RpbmcgdG8gaWxsdXN0cmF0ZSB0aGUgY2x1c3RlciAxIGJ5IGNvbXB1dGluZyBpdHMgcGFyYWdvbnMKCmBgYHtyfQpyZXMuaGNwYzMkZGVzYy5pbmQkcGFyYSRgMWAKYGBgCgpUaGUgcHJvZHVjdCA5NCw5NSwxNDIsNzQsMTQ0IGFyZSBjbG9zZXN0IHRvIHRoZSBjZW50cmUgb2YgdGhlIGNsdXN0ZXIgMSBjZW50cm9pZCBhbmQgdGhlbiwgYXJlIHJlcHJlc2VudGF0aXZlIG9mIHRoaXMgY2x1c3Rlci4gCgoqKlFVRVNUSU9OIDEzICAqKgoKCgpXZSBkaWRuJ3QgY2hvb3NlICRrID0gMiQgYmVjYXVzZSBpbiB0aGlzIGNvbmZpZ3VyYXRpb24sIHRoZSB0d28gZmlyc3QgZWlnZW4gdmVjdG9ycyBkZXNjcmliZSBsZXNzIHRoYW4gOTUlIG9mIHRoZSBkYXRhLiBXZSBkaWRuJ3QgY2hvb3NlICRrPTQkIGJlY2F1c2UgaXQncyBub3QgbmVjZXNzYXJ5IGdpdmVuIHRoZSBmYWN0IHRoYXQgdGhlIGNvbmZpZ3VyYXRpb24gJGs9MyQgaXMgZGVzY3JpYmluZyBhbHJlYWR5IDk1JSBvZiBvdXIgZGF0YS4KCkEgc3RyYXRlZ3kgdG8gYXNzZXNzIHRoZSBzdGFiaWxpdHkgb2YgdGhlIGFwcHJvYWNoIGlzIHRvIGRvIHRoZSBzYW1lIHN0dWR5IHRoYXQgd2UgZGlkIGJ1dCBmb3IgdGhlIGhjcGMgd2l0aCAyIGFuZCA0IGNvbXBlbmVudC4gVGhlbiwgd2Ugd2lsbCBiZSBhYmxlIHRvIGNvbXBhcmUgdGhlIGRpZmZlcmVudCBwLXZhbHVlcyBmb3IgZWFjaCBjb25maWd1cmF0aW9uIGFuZCBzZWUgZ29pbmcgZm9ybSAyIHRvIDMgb3IgZnJvbSAzIHRvIDQgaGF2ZSBhIHNpZ25pZmljYW50IGltcGFjdCBvbiB0aGUgcmVzdWx0cyBvZiBvdXIgY2x1c3RlcmluZy4gQWZ0ZXIgZG9pbmcgdGhpcyBzdHVkeSB3ZSBub3RpY2VkIHRoYXQgd2UgaGF2ZSB0aGUgc2FtZSBwLXZhbHVlIHJlc3VsdHMgZm9yICRrPTMkIGFuZCAkaz00JC4gUGx1cywgd2Ugbm90aWNlZCB0aGF0IHdlIGhhdmUgdGhlICBwLXZhbHVlIHJlc3VsdHMgYXJlIGluIGF2ZXJhZ2UgYmV0dGVyIGZvciAgJGs9MyQgdGhhbiAkaz0yJC4gVGhpcyBpbnNpZ2h0cyBleHBsYWluIHVzIHRoYXQgMyBjb21wb25lbnRzIGlzIHRoZSBiZXN0IGNvbXByb21pc2UgYW5kIGNob2ljZS4gT24gdGhlIHNhbWUgdmVpbiwgd2Ugbm90aWNlZCB0aGF0IHRoZXJlIGEgbm8gZGlmZmVyZW5jZSBpbiB0ZXJtcyBvZiBjbHVzdGVyIGRlc2NyaXB0aW9uIGZvciBhIGNsdXN0ZXJpbmcgb2J0YWluZWQgb24gayBjb21wb25lbnRzIG9yIG9uIHRoZSBpbml0aWFsIGRhdGEuIAoKCioqUVVFU1RJT04gMTQgICoqCgpUaGUgbWV0aG9kb2xvZ3kgdGhhdCB3ZSBoYXZlIHVzZWQgdG8gZGVzY3JpYmUgY2x1c3RlcnMgY2FuIGFsc28gYmUgdXNlZCB0byBkZXNjcmliZSBhIGNhdGVnb3JpY2FsCnZhcmlhYmxlLCBmb3IgaW5zdGFuY2UgdGhlIHN1cHBsaWVyLiAKCgpgYGB7cn0KY2F0ZGVzKGRhdGFzZXQsIG51bS52YXI9IDEpIApgYGAKCldlIHdpbGwgZGVzY3JpYmUgaGVyZSB0aGUgU3VwcGxpZXIgY2F0ZWdvcmljYWwgdmFyaWFibGUgYnV0IHRoZSBtZXRob2QgaXMgdGhlIHNhbWUgZm9yIHRoZSBvdGhlcnMgdmFyaWFibGVzLiAKVXNpbmcgdGhlICByZXN1bHRzIG9mIGNhdGRlcywgd2UgY2FuIGludGVycHJldCB0aGUgZGlmZmVyZW50IHN1cHBsaWVycyBhcyBjbGFzc2VzLiBXZSBub3RpY2UgZmlyc3QgdGhhdCBSYXcuTWF0ZXJpYWwgYW5kIEltcGVybWVhYmlsaXR5IGRlc2NyaWJlIHdlbGwgdGhlICJzdXBwbGllciBjbHVzdGVyaW5nIi4gSWYgd2UgZGVlcCBkaXZlIGludG8gdGhlIGRpZmZlcmVudCBzdXBwbGllcnMgaW5zaWdodHMgd2UgY2FuIGZvciBleGFtcGxlIG5vdGljZSB0aGF0IHRoZSBTdXBwbGllciBBIGlzIGNoYXJhY3Rlcml6ZWQgYnkgKGluIG9yZGVyIG9mIGltcG9ydGFuY2UpIDogClJhdy5NYXRlcmlhbD1QUzsgSW1wZXJtZWFiaWxpdHk9VHlwZSAyLDEgO1NoYXBlPVNoYXBlIDIgYW5kIFJhdy5NYXRlcmlhbD1BQlMuIFRoaXMgdmFyaWFibGVzIGJlc3QgZGVzY3JpYmUgdGhlIFN1cHBsaWVyIEEuIFRoZW4sIHdlIHNlZSB0aGF0IHRoaXMgZGF0YSBpcyB2ZXJ5IGluc2lnaHRmdWxsLiBJbiBmYWN0LCBkb2luZyBhIGNhdGRlcyBvbiBzdXBwbGllciBmb3IgZXhhbXBsZSBhbGxvd3MgdGhlIHVzZXIgdG8gZG8gYSBiZW5jaGVybWFya2luZyBvZiB0aGUgZGlmZmVyZW50IHN1cHBsaWVycy4gCgoqKlFVRVNUSU9OIDE1ICoqCgpUbyBzaW11bHRhbmVvdXNseSB0YWtlIGludG8gYWNjb3VudCBxdWFudGl0YXRpdmUgYW5kIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBpbiB0aGUgY2x1c3RlcmluZyB3ZSB3aWxsCnVzZSB0aGUgSENQQyBmdW5jdGlvbiBvbiB0aGUgcmVzdWx0cyBvZiB0aGUgRkFNRCBvbmVzLiBGQU1EIHN0YW5kcyBmb3IgRmFjdG9yaWFsIEFuYWx5c2lzIG9mIE1peGVkCkRhdGEgYW5kIGlzIGEgUENBIGRlZGljYXRlZCB0byBtaXhlZCBkYXRhLiAKCgpgYGB7cn0KcmVzLmZhbWQgPSBGQU1EKGRhdGFzZXQsIG5jcCA9IDEwLCBzdXAudmFyID0gYygxMCkgKQoKYGBgCgpgYGB7cn0KcmVzLmZhbWQkZWlnCmBgYAoKClRoaXMgRkFNRCBhbmFseXNpcyBoYXMgbWFueSBpbXBhY3RzIG9uIHRoZSBkaWZmZXJlbnQgcmVzdWx0cy4gRmlyc3QsIHRoZSBvdmVyYWxsIGNvcnJlbGF0aW9uIGJldHdlZW4gdmFyaWFibGVzIGRlY3JlYXNlIChpbiBmYWN0LCBGQU1EIGFuYWx5c2lzIHRha2VzIGludG8gYWNjb3VudCBtb3JlIHZhcmlhYmxlcykuIFdlIGhhdmUgdG8gdGFrZSAxMCBjb21wb25lbnRzIChhZ2FpbnN0IDMgZm9yIHRoZSBQQ0EpIHRvIHJlYWNoIDk1JSBpbiB0ZXJtcyBvZiBjdW11bGF0aXZlIHZhcmlhbmNlLiBNb3Jlb3ZlciwgdGhpcyBhbmFseXNpcyBzaG93IHVzIHRoYXQgdGhlIFByaWNlIGlzIHVuY29ycmVsYXRlZCB3aXRoIFN1cHBsaWVyLCBGaW5pc2hpbmcsIG5iLm9mLnBpZWNlcyBhbmQgTWF0dXJlLlZvbHVtZS4gSG93ZXZlciwgSW1wZXJtZWFiaWxpdHkgYW5kIFJhdyBtYXRlcmlhbCBkaXJlY3RseSBpbmZsdWVuY2UgdGhlIHByaWNlIG9mIHRoZSBwcm9kdWN0LiBTaGFwZSBpcyBwYXJ0aWFsbHkgY29ycmVsYXRlZCB3aXRoIHRoZSBwcmljZS4KCgoKCmBgYHtyfQpyZXMuaGNwYyA8LSBIQ1BDKHJlcy5mYW1kLCBuYi5jbHVzdCA9IC0xLCBncmFwaCA9ICBGQUxTRSkKcGxvdC5IQ1BDKHJlcy5oY3BjLCBjaG9pY2UgPSAibWFwIiwgZHJhdy50cmVlID0gRkFMU0UsIHNlbGVjdCA9ICJkcmF3biIsIHRpdGxlID0gJycpCmBgYAoKV2Ugc2VlIGhlcmUgdGhhdCBhZGRpbmcgbW9yZSB2YXJpYWJsZXMgaW4gdGhlIGFuYWx5c2lzIHJlZHVjZSB0aGUgcXVhbGl0eSBvZiB0aGUgY2x1c3RlcmluZyBpbiBjb21wYXJhaXNvbiB3aXRoIHRoZSBsYXN0IG9uZS4gCgoKKipRVUVTVElPTiAxNioqCgpMZXQncyBub3cgcGVyZm9ybSBhIG1vZGVsIHRvIHByZWRpY3QgdGhlIFByaWNlIDoKCgpgYGB7cn0KCmxpYnJhcnkoZ2JtKQoKaW5kZXhfdGVzdCA8LSBzYW1wbGUoeD0xOm5yb3coZGF0YXNldCksIHNpemU9Zmxvb3IoMC4zKm5yb3coZGF0YXNldCkpKQp0ZXN0aW5nX2RhdGFfc2V0IDwtIGRhdGFzZXRbaW5kZXhfdGVzdCxdCnRyYWluaW5nX2RhdGFfc2V0IDwtIGRhdGFzZXRbLWluZGV4X3Rlc3QsXQoKZml0IDwtIGxtKFByaWNlIH4gRGlhbWV0ZXIgKyBMZW5ndGggKyB3ZWlnaHQgKyBSYXcuTWF0ZXJpYWwgKyBJbXBlcm1lYWJpbGl0eSArIFNoYXBlLCBkYXRhPSB0cmFpbmluZ19kYXRhX3NldCkKc3VtbWFyeShmaXQpCnByZWRpY3RlZF9wcmljZSA8LSBwcmVkaWN0LmxtKGZpdCx0ZXN0aW5nX2RhdGFfc2V0KQpyZWFsX3ByaWNlIDwtIHRlc3RpbmdfZGF0YV9zZXQkUHJpY2UKUHJlZGljdGlvbiA8LSAgZGF0YV9mcmFtZShwcmVkaWN0ZWRfcHJpY2UscmVhbF9wcmljZSkgJT4lIG11dGF0ZShkaWZmZXJlbmNlID0gYWJzKHByZWRpY3RlZF9wcmljZS1yZWFsX3ByaWNlKSkKCgoKCgoKYGBgCgpUaGUgbW9kZWwgZ2VuZXJhdGVkIGhlcmUgYnkga2VlcGluZyBvbmx5IHZhcmlhYmxlcyB0aGF0IGV4cGxhaW4gdGhlIHByaWNlIGFsbG93cyB1cyB0byBoYXZlIGFuIEFkanVzdGVkIFItc3F1YXJlZCBvZiA3NS45OCAlIChmb3IgdGhpcyBzcGVjaWZpYyB0cmFpbmluZyBkYXRhc2V0IGdlbmVyYXRlZCkKCmBgYHtyfQpwIDwtIGdncGxvdChQcmVkaWN0aW9uLCBhZXMoeD0gcmVhbF9wcmljZSkpICsgZ2VvbV9wb2ludChhZXMoeT0gcHJlZGljdGVkX3ByaWNlKSkgKyBnZW9tX2xpbmUoYWVzKHk9IHJlYWxfcHJpY2UpKQoKcApgYGAKClRoaXMgZ3JhcGggc2hvd3MgdXMgdGhlIHBvaW50cyB0aGF0IGFyZSBvdmVyZXN0aW1hdGVkL3VuZGVyZXN0aW1hdGVkIGZvciB0aGUgdGVzdGluZyBkYXRhc2V0LiBJdCBzZWVtcyB0aGF0IGluIHRoaXMgZXhhbXBsZSBsb3cgcHJpY2VzIGFyZSBvdmVyZXN0aW1hdGVkIGFuZCBoaWdoIHByaWNlcyBhcmUgdW5kZXJlc3RpbWF0ZWQuIAoKCgpgYGB7cn0KcXVhbnRpbGUoUHJlZGljdGlvbiRkaWZmZXJlbmNlKQpgYGAKCkZvciB0aGUgcGVyZm9ybWFuY2UsIHdlIGNhbiBzYXkgdGhhdCB3ZSBlc3RpbWF0ZSB3aXRoIGxlc3MgdGhhbiAyIGNlbnRpbWUgZXJyb3IgNTAlIG9mIHRoZSBkYXRhIGFuZCB3aXRoIGxlc3MgdGhhbiA0IGNlbnRpbWUgZXJyb3IgNzUlIG9mIHRoZSBkYXRhLiAKClRoZSBwcmV2aW91cyBhbmFseXNpcyBjYW4gaGVscCB1cyBpbnRlcnByZXQgdGhpcyByZXN1bHRzIGJlY2F1c2UgaXQgd2lsbCBiZSBwb3NzaWJsZSB0byBsaW5rIGFuIG92ZXIvdW5kZXIgZXN0aW1hdGVkIHBvaW50IHdpdGggaXRzIHBvc2l0aW9uIGluIHRoZSBpbmRpdmlkdWFsIGZhY3RvciBtYXAgYW5kIHRoZW4gdW5kZXJzdGFuZCB0aGF0IHRoaXMgcG9pbnQgaXMgbm90IGxpbmtlZCB3aXRoIHRoZSBkcml2ZXJzIG9mIHRoZSBwcmljZS4KCgoqKlFVRVNUSU9OIDE3KioKCkl0J3Mgbm90IHNtYXJ0IHRvIGRvIG9uZSBtb2RlbCBwZXIgc3VwcGxpZXIgYmVjYXVzZSA6IAoKKiBUaGVyZSBpcyBubyBwcmljZSBkZXBlbmRlbmN5IG9uIHN1cHBsaWVycy4KKiBJdCB3aWxsIHJlZHVjZSB0aGUgbnVtYmVyIG9mIHBvaW50cyBmb3IgZWFjaCByZWdyZXNzaW9uIChFc3BlY2lsbGF5IGZvciBTdXBwbGllciBDKS4KKiBXZSB3aWxsIGxvb3NlIGluZm9ybWF0aW9uLiBGb3IgaW5zdGFuY2UsIG1heWJlIHRoYXQgdGhlcmUgd2lsbCBiZSBhIFN1cHBsaWVyIEEgcHJvZHVjdCB0byAgcHJlZGljdCB3aGljaCBoYXZlIHRoZSBzYW1lIGNoYXJhY3RlcmlzdGljcyB0aGFuIGEgU3VwcGxpZXIgQiBwcm9kdWN0LiAKCgoqKlFVRVNUSU9OIDE4KioKClRoZXNlIGRhdGEgY29udGFpbmVkIG1pc3NpbmcgdmFsdWVzLiBPbmUgcmVwcmVzZW50YXRpdmUgaW4gdGhlIGNvbXBhZ255IHN1Z2dlc3RzIGVpdGhlciB0byBwdXQgMCBpbiB0aGUKbWlzc2luZyBjZWxscyBvciB0byBpbXB1dGUgd2l0aCB0aGUgbWVkaWFuIG9mIHRoZSB2YXJpYWJsZXMuIENvbW1lbnQuIEZvciB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHdpdGgKbWlzc2luZyB2YWx1ZXMsIGl0IGlzIGRlY2lkZWQgdG8gY3JlYXRlIGEgbmV3IGNhdGVnb3J5IOKAnG1pc3NpbmfigJ0uIENvbW1lbnQuCgpUaGUgZmlyc3QgaWRlYSB3aWxsIGNoYW5nZSBhbGwgdGhlIHN0cnVjdHVyZSBvZiBvdXIgZGF0YSBiZWNhdWUgaXQgd2lsbCBjaGFuZ2UgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gCnRoZSBkaWZmZXJlbnQgdmFyaWFibGVzLiBUaGUgc2Vjb25kIGlkZWEgd2lsbCBub3QgZml4IHRoZSBwcm9ibGVtIGFuZCB3aWxsIGNoYW5nZSBub3RoaW5nIGJlY2F1c2UgdGhpcyBtZXRob2Qgb25seSByZW5hbWVzICB0aGUgIk5BIiBjYXRlZ29yeSB0byBhICJtaXNzaW5nIiBjYXRlZ29yeS4KCgoKCgoKCgoKCgoKCgo=